/**
 * Copyright (c) 2010 Pablo Muñoz Galindo
 * <p>
 * Este software está licenciado bajo los términos de la licencia GNU GPLv3
 * <br>
 * Para más información:
 * <a href="http://www.gnu.org/licenses/gpl-3.0-standalone.html"> http://www.gnu.org/licenses/gpl-3.0-standalone.html </a>
 * <br>
 * Este software es distribuido "como es", no ofreciendo garantías de ningún tipo. La responsabilidad
 * de su uso queda bajo la cuenta y riesgo del propio usuario.
 *
 */
package esteganografo.core;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.util.encoders.Base64;

/**
 * Clase para almacenar aquellos métodos que puedan ser útiles para el resto del programa
 *
 * @author Pablo Muñoz Galindo
 * @version 0.1b
 */
public class Utilities {

    /**
     * Método para imprimir mapas de memoria a partir de un array de bytes
     *
     * @param contenido array de bytes
     */
    public static void printHexContent(byte[] contenido) {
        for (int i = 0; i < contenido.length; i++) {
            byte b1 = (byte) (254 & 0xFF);
            String representacion = Integer.toHexString((int) contenido[i] & 0xFF);
            if (representacion.length() == 1) {
                representacion = "0" + representacion;
            }
            System.out.print(representacion);
            System.out.print("  ");
            if ((i + 1) % 16 == 0) {
                System.out.println();
            }
        }
        System.out.println();
    }


    /*
     * MÉTODOS ÚTILES PARA CIFRAR Y DESCIFRAR INFORMACIÓN CON AES-128 Y A PARTIR
     * DE UN HASH MD5 GENERADO CON UNA CLAVE STRING
     *
     * /

    /**
     * Método para cifrar un String con AES128 generando la clave por medio del hash MD5 de un password
     * @param inputBytes
     * @param password
     * @return
     * @throws Exception
     */
    public static String cifraConAES128 (String mensajeOriginal, String password) throws Exception {
        byte[] inputBytes = mensajeOriginal.getBytes();
        byte[] clave = Utilities.generaMD5(password);
        BufferedBlockCipher cifrador = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
        cifrador.init(true, new KeyParameter(clave));
        ByteArrayInputStream in = new ByteArrayInputStream(inputBytes);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int inLength;
        int outLength;
        byte[] inputBuffer = new byte[1024];
        byte[] outputBuffer = new byte[cifrador.getOutputSize(inputBuffer.length)];

        while ((inLength = in.read(inputBuffer)) > -1) {
            outLength = cifrador.processBytes(inputBuffer, 0, inLength, outputBuffer, 0);
            if (outLength > 0) {
                out.write(outputBuffer, 0, outLength);
            }
        }

        outLength = cifrador.doFinal(outputBuffer, 0);
        if (outLength > 0) {
            out.write(outputBuffer, 0, outLength);
        }

        return new String(Base64.encode(out.toByteArray()));
    }

    
    /**
     * Método para descifrar un String con AES128 generando la clave por medio del hash MD5 de un password
     * @param inputBytes
     * @param password
     * @return
     * @throws Exception
     */
    public static String descifraConAES128 (String mensaje, String password) throws Exception {
        byte[] inputBytes = Base64.decode(mensaje.getBytes());
        byte[] clave = Utilities.generaMD5(password);
        BufferedBlockCipher cifrador = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
        cifrador.init(false, new KeyParameter(clave));
        ByteArrayInputStream in = new ByteArrayInputStream(inputBytes);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int inLength;
        int outLength;
        byte[] inputBuffer = new byte[1024];
        byte[] outputBuffer = new byte[cifrador.getOutputSize(inputBuffer.length)];

        while ((inLength = in.read(inputBuffer)) > -1) {
            outLength = cifrador.processBytes(inputBuffer, 0, inLength, outputBuffer, 0);
            if (outLength > 0) {
                out.write(outputBuffer, 0, outLength);
            }
        }

        outLength = cifrador.doFinal(outputBuffer, 0);
        if (outLength > 0) {
            out.write(outputBuffer, 0, outLength);
        }
        return new String(out.toByteArray());
    }

    /**
     * Método que sirve para comprobar el hash de un mensaje a partir de un fichero con el
     * hash original.
     *
     * @param mensaje_orig Mensaje a comprobar
     * @param fichero_md5 Fichero con el hash del mensaje original
     * @return true si el hash es correcto
     */
    public static boolean compruebaHashMD5(String mensaje, String fichero_md5) {
        try {
            //Generamos la suma del mensaje decodificado
            byte[] suma = generaMD5(mensaje);

            //Leemos la suma del mensaje original
            File md5_file = new File(fichero_md5);
            FileInputStream fis = new FileInputStream(md5_file);
            byte[] suma_original = new byte[(int) md5_file.length()];
            fis.read(suma_original);

            //Finalmente devolvemos el resultado de la comprobación
            return verificaSuma(suma_original, suma);

        } catch (Exception e1) {
            return false;
        }
    }

    /**
     * Método para generar el HASH MD5 que corresponde a un texto
     * @param texto
     * @return Hash MD5
     */
    public static byte[] generaMD5(String texto){
        MD5Digest md5 = new MD5Digest();
        byte[] bytes = texto.getBytes();
        md5.update(bytes, 0, bytes.length);
        byte[] suma = new byte[md5.getDigestSize()];
        md5.doFinal(suma, 0);
        return suma;
    }

    /**
     * Método privado para ayudar a la comprobación de los hashes
     */
    private static boolean verificaSuma(byte[] b1, byte[] b2) {
        if (b1.length != b2.length) {
            return false;
        }
        for (int i = 0; i < b1.length; i++) {
            if (b1[i] != b2[i]) {
                return false;
            }
        }
        return true;
    }


    
    /*
     * MÉTODOS ÚTILES PARA GENERAR FILTROS PARA LOS CUADROS DE DIÁLOGO "EXAMINAR"
     * 
     */

    /**
     * Método que devuelve una instancia de un FileFilter para extensión WAV
     * @return
     */
    public static CustomWAVFileFilter getFiltroExtensionWAV() {
        return new CustomWAVFileFilter();
    }

    /**
     * Método que devuelve una instancia de un FileFilter para extensión BMP
     * @return
     */
    public static CustomBMPFileFilter getFiltroExtensionBMP(){
        return new CustomBMPFileFilter();
    }

    /**
     * Método que devuelve una instancia de un FileFilter para extensión MD5
     * @return
     */
    public static CustomMD5FileFilter getFiltroExtensionMD5(){
        return new CustomMD5FileFilter();
    }

    
    /**
     * Clase interna que implementa un Filtro para fichero WAV
     */
    public static class CustomWAVFileFilter extends javax.swing.filechooser.FileFilter {

        @Override
        public boolean accept(File file) {
            return file.isDirectory() | file.getAbsolutePath().endsWith(".wav");
        }

        @Override
        public String getDescription() {
            return "Fichero de audio (*.wav)";
        }
    }


    /**
     * Clase interna que implementa un Filtro para fichero BMP
     */
    public static class CustomBMPFileFilter extends javax.swing.filechooser.FileFilter {

        @Override
        public boolean accept(File file) {
            return file.isDirectory() | file.getAbsolutePath().endsWith(".bmp");
        }

        @Override
        public String getDescription() {
            return "Bitmaps (*.bmp)";
        }
    }


    /**
     * Clase interna que implementa un Filtro para fichero MD5
     */
    public static class CustomMD5FileFilter extends javax.swing.filechooser.FileFilter {

        @Override
        public boolean accept(File file) {
            return file.isDirectory() | file.getAbsolutePath().endsWith(".md5");
        }

        @Override
        public String getDescription() {
            return "Hash MD5 (*.md5)";
        }
    }


}
